﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AstarTest.Test
{
    class TileMap
    {
        public int Columns { get; set; } // Nr of columns
        public int Rows { get; set; } // Nr of rows
        public Tile[,] Tiles { get; private set; }
        private int[,] tiles;
        private Tile goal;
        private List<Tile> opened = new List<Tile>();
        private List<Tile> closed = new List<Tile>();
        private List<Tile> bestList = new List<Tile>();

        public TileMap()
        {
            // This twodimensional array represents the map of tiles.
            // 0 = nothing  1 = tile    (0, 0) = start  (9, 9) = end
            //tiles = new int[10, 10] { 
            ////        ---> columns -->
            //{ 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
            //{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, // r
            //{ 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, // o
            //{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, // w
            //{ 1, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, // s
            //{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
            //{ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, 
            //{ 0, 0, 0, 1, 0, 0, 1, 0, 1, 0 }, 
            //{ 0, 0, 0, 0, 1, 0, 1, 0, 1, 0 }, 
            //{ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 }, 
            //};

            tiles = new int[20, 20] { 
            //        ---> columns -->
            { 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
            { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, // r
            { 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, // o
            { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, // w
            { 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, // s
            { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
            { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, 
            { 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0 }, 
            { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0 }, 
            { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
            { 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
            { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, // r
            { 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, // o
            { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, // w
            { 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, // s
            { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
            { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, 
            { 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0 }, 
            { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0 }, 
            { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 }
            };
            this.Rows = tiles.GetLength(0);
            this.Columns = tiles.GetLength(1);
            this.Tiles = new Tile[Rows, Columns];

            // Generate the tiles
            for (int row = 0; row < this.Rows; row++)
            {
                for (int column = 0; column < this.Columns; column++)
                {
                    this.Tiles[row, column] = new Tile(column, row, this.tiles[row, column], this);
                    Console.Write(this.tiles[row, column]);
                }
                Console.WriteLine();
            }

            // Initialize the start and goal tile
            Tiles[0, 0].IsStart = true; // Start tile = Top left
            this.goal = Tiles[19, 19]; // End tile = Lower right
            goal.IsEnd = true;

            GenerateAdjacencies();
        }

        /// <summary>
        /// Generate the adjacencies for all tiles.
        /// </summary>
        private void GenerateAdjacencies()
        {
            for (int i = 0; i < Rows; i++)
            {
                for (int j = 0; j < Columns; j++)
                {
                    Tiles[i, j].CalculateAdjacencies();
                }
            }
        }

        /// <summary>
        /// Find the best path using the A*-algorithm.
        /// </summary>
        public void FindBestPath()
        {

            Console.WriteLine("Calculating best path...");
            long startTime = DateTime.Now.Ticks;
            long endTime;
            long timeElapsed;
            HashSet<Tile> adjacencies = Tiles[0, 0].Adjacencies;
            foreach (Tile adjacency in adjacencies)
            {
                adjacency.Parent = Tiles[0, 0];
                if (adjacency.IsStart == false)
                {
                    opened.Add(adjacency);
                }
            }

            while (opened.Count > 0)
            {
                Tile best = findBestPassThrough();
                opened.Remove(best);
                closed.Add(best);
                if (best.IsEnd)
                {
                    Console.WriteLine("Found Goal");
                    populateBestList(goal);
                    endTime = DateTime.Now.Ticks;
                    timeElapsed = (endTime - startTime);
                    Console.WriteLine("Time elapsed: " + timeElapsed);
                    Print();
                    return;
                }
                else
                {
                    HashSet<Tile> neighbors = best.Adjacencies;
                    foreach (Tile neighbor in neighbors)
                    {
                        if (opened.Contains(neighbor))
                        {
                            Tile tmpSquare = new Tile(neighbor.X,
                                    neighbor.Y, neighbor.Type, this);
                            tmpSquare.Parent = best;
                            if (tmpSquare.getPassThrough(goal) >= neighbor
                                    .getPassThrough(goal))
                            {
                                continue;
                            }
                        }

                        if (closed.Contains(neighbor))
                        {
                            Tile tmpSquare = new Tile(neighbor.X,
                                    neighbor.Y, neighbor.Type, this);
                            tmpSquare.Parent = best;
                            if (tmpSquare.getPassThrough(goal) >= neighbor
                                    .getPassThrough(goal))
                            {
                                continue;
                            }
                        }

                        neighbor.Parent = best;

                        opened.Remove(neighbor);
                        closed.Remove(neighbor);
                        opened.Insert(0, neighbor);
                    }
                }
            }
            endTime = DateTime.Now.Ticks;
            timeElapsed = (endTime - startTime);
            Console.WriteLine("Time elapsed: " + timeElapsed);
            Console.WriteLine("No Path to goal");
        }

        private void populateBestList(Tile tile)
        {

            bestList.Add(tile);
            if (tile.Parent.IsStart == false)
            {
                populateBestList(tile.Parent);
            }

            return;
        }

        private Tile findBestPassThrough()
        {

            Tile best = null;
            foreach (Tile tile in opened)
            {
                if (best == null
                        || tile.getPassThrough(goal) < best.getPassThrough(goal))
                {
                    best = tile;
                }
            }

            return best;
        }

        /// <summary>
        /// Prints the map and the calculated path.
        /// </summary>
        private void Print()
        {
            for (int row = 0; row < this.Rows; row++)
            {
                for (int column = 0; column < this.Columns; column++)
                {
                    Tile tile = this.Tiles[row, column];
                    if (tile.IsStart)
                        Console.Write("S");
                    else if (tile.IsEnd)
                        Console.Write("E");
                    else if (this.bestList.Contains(tile))
                        Console.Write(".");
                    else
                        Console.Write(tile.Type);
                }
                Console.WriteLine();
            }
        }
    }
}
